using System;
using Microsoft.Win32;

using Monitorian.Core.Models.Monitor;

namespace Monitorian.Core.Models.Watcher;

internal class PowerWatcher : IDisposable
{
	private Action<ICountEventArgs> _onPowerChanged;

	private void RaisePowerModeChanged(PowerModes mode, int count) =>
		_onPowerChanged?.Invoke(new PowerModeChangedCountEventArgs(mode, count));

	private void RaiseDisplayStateChanged(DisplayStates state, int count) =>
		_onPowerChanged?.Invoke(new DisplayStateChangedCountEventArgs(state, count));

	private void RaiseSystemStarted(int count) =>
		_onPowerChanged?.Invoke(new SystemStartedCountEventArgs(count));

	private SystemEventsComplement _complement;

	private class PowerDataWatcher<T> : TimerWatcher
	{
		private readonly Action<T, int> _action;

		public PowerDataWatcher(Action<T, int> action, params int[] intervals) : base(intervals) => this._action = action;

		public T Data { get; private set; }

		public void TimerStart(T data)
		{
			this.Data = data;
			base.TimerStart();
		}

		protected override void TimerTick() => _action.Invoke(Data, Count);
	}

	private readonly PowerDataWatcher<PowerModes> _resumeWatcher;
	private readonly PowerDataWatcher<PowerModes> _statusWatcher;
	private readonly PowerDataWatcher<DisplayStates> _stateWatcher;

	private class SystemStartedWatcher : TimerWatcher
	{
		private readonly Action<int> _action;

		public SystemStartedWatcher(Action<int> action, params int[] intervals) : base(intervals) => this._action = action;

		protected override void TimerTick() => _action.Invoke(Count);

		protected override void TimerFinish()
		{
			this.Dispose();

			if (_action.Target is PowerWatcher watcher)
				watcher._startedWatcher = null;
		}
	}

	private SystemStartedWatcher _startedWatcher;

	public PowerWatcher()
	{
		// Conform invocation timings so that the action would be executed efficiently when
		// multiple and different events are fired almost simultaneously.
		_resumeWatcher = new PowerDataWatcher<PowerModes>(this.RaisePowerModeChanged, 5, 5, 10, 10, 30);
		_statusWatcher = new PowerDataWatcher<PowerModes>(this.RaisePowerModeChanged, 1, 4);
		_stateWatcher = new PowerDataWatcher<DisplayStates>(this.RaiseDisplayStateChanged, 5, 5);
	}

	public void Subscribe(Action<ICountEventArgs> onPowerChanged, bool? isSystemStarted)
	{
		this._onPowerChanged = onPowerChanged ?? throw new ArgumentNullException(nameof(onPowerChanged));
		SystemEvents.PowerModeChanged += OnPowerModeChanged;

		var (powerSettingGuids, powerSettingChanged) = PowerManagement.GetOnPowerSettingChanged();

		_complement = new SystemEventsComplement();
		_complement.PowerSettingChanged += (_, e) => OnDisplayStateChanged(powerSettingChanged.Invoke(e));
		_complement.RegisterPowerSettingEvent(powerSettingGuids);

		if (isSystemStarted is true)
		{
			_startedWatcher = new SystemStartedWatcher(this.RaiseSystemStarted, 30, 30);
			_startedWatcher.TimerStart();
		}
	}

	private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
	{
		switch (e.Mode)
		{
			case PowerModes.Resume:
				RaisePowerModeChanged(e.Mode, 0);
				_resumeWatcher.TimerStart(e.Mode);
				break;
			case PowerModes.Suspend:
				_resumeWatcher.TimerStop();
				RaisePowerModeChanged(e.Mode, 0);
				break;
			default:
				_statusWatcher.TimerStart(e.Mode);
				break;
		}
	}

	public bool IsDisplayOff { get; private set; }

	private async void OnDisplayStateChanged(DisplayStates state)
	{
		await OperationRecorder.RecordAsync($"Display {state}");

		switch (state)
		{
			case DisplayStates.On:
			case DisplayStates.Dimmed:
				IsDisplayOff = false;
				RaiseDisplayStateChanged(state, 0);
				_stateWatcher.TimerStart(state);
				break;
			case DisplayStates.Off:
				IsDisplayOff = true;
				_stateWatcher.TimerStop();
				RaiseDisplayStateChanged(state, 0);
				break;
		}
	}

	#region IDisposable

	private bool _isDisposed = false;

	public void Dispose()
	{
		Dispose(true);
		GC.SuppressFinalize(this);
	}

	protected virtual void Dispose(bool disposing)
	{
		if (_isDisposed)
			return;

		if (disposing)
		{
			// Free any other managed objects here.
			SystemEvents.PowerModeChanged -= OnPowerModeChanged;
			_complement?.UnregisterPowerSettingEvent();
			_resumeWatcher.Dispose();
			_statusWatcher.Dispose();
			_stateWatcher.Dispose();
			_startedWatcher?.Dispose();
		}

		// Free any unmanaged objects here.
		_isDisposed = true;
	}

	#endregion
}

public class PowerModeChangedCountEventArgs : CountEventArgs<PowerModes>
{
	public PowerModeChangedCountEventArgs(PowerModes mode, int count) : base(mode, count)
	{ }
}

public class DisplayStateChangedCountEventArgs : CountEventArgs<DisplayStates>
{
	public DisplayStateChangedCountEventArgs(DisplayStates state, int count) : base(state, count)
	{ }
}

public class SystemStartedCountEventArgs : CountEventArgs
{
	public override string Description => $" SystemStarted {Count}";

	public SystemStartedCountEventArgs(int count) : base(count)
	{ }
}